// $Id: DataMapper.cs 10 2008-02-29 15:58:02Z mcartoixa $
/*
 *  Copyright (C) 2007 Noury Solutions
 *
 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Lesser General Public
 *  License as published by the Free Software Foundation; either
 *  version 3 of the License, or (at your option) any later version.
 *
 *  This library is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *  Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public
 *  License along with this library. If not, see <http://www.gnu.org/licenses/>.
 *
 */
/*! \file
 * \brief Interfaces of Data Mappers.
 *
 * \author $Author: mcartoixa $
 * \date $Date: 2008-02-29 16:58:02 +0100 (ven., 29 févr. 2008) $
 * \version $Revision: 10 $
 */

using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;

namespace Salamanca.DataAccess
{



    ///////////////////////////////////////////////////////////////////////////
    ///
    /// <summary>Interface of a <include file="../doc/Global.doc.xml" path="doc/template[@name='DataMapper']/*" />.</summary>
    ///
    ///////////////////////////////////////////////////////////////////////////

    public interface IDataMapper
    {

        /// <summary>Finds the <include file="../doc/Global.doc.xml" path="doc/template[@name='DomainModel']/*" /> associated to the specified key.</summary>
        IDomainModel Find(IPrimaryKey key, Collections.DataMapperCollection dataMappers);

        /// <summary>Refreshes the data of the specified <include file="../doc/Global.doc.xml" path="doc/template[@name='DomainModel']/*" /> instance.</summary>
        /// <param name="domainModel">The <include file="../doc/Global.doc.xml" path="doc/template[@name='DomainModel']/*" /> instance to be refreshed.</param>
        void Refresh(IDomainModel domainModel);

        /// <summary>Deletes the data of the specified <include file="../doc/Global.doc.xml" path="doc/template[@name='DomainModel']/*" /> instance.</summary>
        //        /// <param name="domainModel">The <include file="../doc/Global.doc.xml" path="doc/template[@name='DomainModel']/*" /> instance to be deleted.</param>
        void Delete(IDomainModel domainModel, IPersistenceContext context);
        /// <summary>Saves the data of the specified <include file="../doc/Global.doc.xml" path="doc/template[@name='DomainModel']/*" /> instance.</summary>
        //        /// <param name="domainModel">The <include file="../doc/Global.doc.xml" path="doc/template[@name='DomainModel']/*" /> instance to be saved.</param>
        void Save(IDomainModel domainModel, IPersistenceContext context);

        /// <summary>Gets the <include file="../doc/Global.doc.xml" path="doc/template[@name='DomainModel']/*" /> corresponding to the specified data.</summary>
        IDomainModel GetDomainModel(IDomainModelDataTransferObject data, Collections.DataMapperCollection dataMappers);

        /// <summary>Gets the data associated to the specified <include file="../doc/Global.doc.xml" path="doc/template[@name='DomainModel']/*" />.</summary>
        IDomainModelDataTransferObject GetData(IDomainModel domainModel, bool deep);
        /// <summary>Assigns the specified data to the specified <include file="../doc/Global.doc.xml" path="doc/template[@name='DomainModel']/*" />.</summary>
        void SetData(IDomainModel domainModel, IDomainModelDataTransferObject data);
    }



    ///////////////////////////////////////////////////////////////////////////
    ///
    /// <summary>Base class for a <include file="../doc/Global.doc.xml" path="doc/template[@name='DataMapper']/*" />.</summary>
    ///
    ///////////////////////////////////////////////////////////////////////////

    public abstract class DataMapperBase<TDomainModel, TPrimaryKey, TDataTransferObject>:
        IDataMapper
        where TDomainModel:
            DomainModel<TPrimaryKey, TDataTransferObject>
        where TPrimaryKey:
            IPrimaryKey,
            new()
        where TDataTransferObject:
            class, // SOAP serialization does not accept structs...
            IDataTransferObject,
            new()
    {

        /// <summary>Constructor.</summary>
        private DataMapperBase()
        {
        }

        /// <summary>Constructor.</summary>
        /// <param name="instanceCreator">Member used to create new instances for the type. Must not be <c>null</c>.</param>
        /// <param name="cacheManager">A cache manager. Must not be <c>null</c>.</param>
        /// <exception cref="ArgumentNullException">Raised if <paramref name="instanceCreator" /> is <c>null</c>.</exception>
        protected DataMapperBase(DomainModelCreator<TDomainModel> instanceCreator, ICacheManager cacheManager)
        {
            Debug.Assert(instanceCreator!=null);
            if (instanceCreator==null)
                throw new ArgumentNullException("instanceCreator", Resources.SR.NullDomainModelCreator);
            Debug.Assert(cacheManager!=null);
            if (cacheManager==null)
                throw new ArgumentNullException("cacheManager");

            _InstanceCreator=instanceCreator;
            _CacheManager=cacheManager;
        }

        /// <summary>Gets the <include file="../doc/Global.doc.xml" path="doc/template[@name='DomainModel']/*" /> corresponding to the specified data.</summary>
        public TDomainModel GetDomainModel(IDomainModelDataTransferObject data, Collections.DataMapperCollection dataMappers)
        {
            if (data==null)
                return default(TDomainModel);

            TDomainModel dm=CreateDomainModel(data.Data);
            ((IDomainModel)dm).DataMappers=dataMappers;
            SetData(dm, data);

            return GetInstance((TPrimaryKey)((IDomainModel)dm).Id, dm);
        }

        /// <summary>Finds the <include file="../doc/Global.doc.xml" path="doc/template[@name='DomainModel']/*" /> associated to the specified key.</summary>
        public abstract TDomainModel Find(TPrimaryKey key, Collections.DataMapperCollection dataMappers);

        /// <summary>Refreshes the data of the specified <include file="../doc/Global.doc.xml" path="doc/template[@name='DomainModel']/*" /> instance.</summary>
        protected abstract void Refresh(TDomainModel domainModel);

        /// <summary>Deletes the data of the specified <include file="../doc/Global.doc.xml" path="doc/template[@name='DomainModel']/*" /> instance.</summary>
        protected abstract void Delete(TDomainModel domainModel, IPersistenceContext context);

        /// <summary>Saves the data of the specified <include file="../doc/Global.doc.xml" path="doc/template[@name='DomainModel']/*" /> instance.</summary>
        protected abstract void Save(TDomainModel domainModel, IPersistenceContext context);

        /// <summary>Gets the data associated to the specified <include file="../doc/Global.doc.xml" path="doc/template[@name='DomainModel']/*" />.</summary>
        protected virtual IDomainModelDataTransferObject GetData(TDomainModel domainModel, bool deep)
        {
            return new DomainModelDataTransferObject<TDataTransferObject>(
                (TDataTransferObject)((ITransferable)domainModel).GetData(),
                (deep ? ((IDomainModel)domainModel).Childs : null),
                ((IDomainModel)domainModel).IsLoaded,
                ((IDomainModel)domainModel).IsModified,
                ((IDomainModel)domainModel).IsNew
            );
        }

        /// <summary>Assigns the specified data to the specified <include file="../doc/Global.doc.xml" path="doc/template[@name='DomainModel']/*" />.</summary>
        protected virtual void SetData(TDomainModel domainModel, IDomainModelDataTransferObject data)
        {
            ((ITransferable)domainModel).SetData(data.Data);
            domainModel.SetLoaded(data.IsLoaded);
            domainModel.SetModified(((DomainModelDataTransferObject<TDataTransferObject>)data).IsModified);
            domainModel.SetNew(((DomainModelDataTransferObject<TDataTransferObject>)data).IsNew);
        }

        /// <summary>Transfers the specified <param name="data" /> to the specified <param name="domainModel" />.</summary>
        internal protected virtual void TransferData(TDomainModel domainModel, TDataTransferObject data)
        {
            SetData(domainModel, new DomainModelDataTransferObject<TDataTransferObject>(data, null, true, false, false));
        }



        /// <summary>Creates a new <include file="../doc/Global.doc.xml" path="doc/template[@name='DomainModel']/*" /> instance.</summary>
        protected TDomainModel CreateDomainModel(IDataTransferObject data)
        {
            return _InstanceCreator.Invoke(data);
        }

        /// <summary>Gets the Domain Model instance with the specified key.</summary>
        /// <remarks>
        /// <para>If </para>
        /// <para>This method can be overriden to look for instances in a cache, or
        /// in an <a href="http://www.martinfowler.com/eaaCatalog/identityMap.html">Identity Map</a> for example.</para>
        /// <para>This method always returns <paramref name="newInstance" />.</para>
        /// </remarks>
        /// <param name="key">The key against which the command is created.</param>
        /// <param name="newInstance">An optional instance to return if no instance can be found.</param>
        /// <returns>The found <include file="../doc/Global.doc.xml" path="doc/template[@name='DomainModel']/*" /> instance,
        /// <paramref name="newInstance" /> if no instance could be found (default).</returns>
        protected TDomainModel GetInstance(TPrimaryKey key, TDomainModel newInstance)
        {
            object ret=default(TDomainModel);
            if ((!CacheManager.TryGetValue(key, out ret)) && (newInstance!=null))
            {
                IDomainModel dm=newInstance as IDomainModel;
                if ((dm==null) || (!dm.IsNew))
                    CacheManager.Add(key, newInstance);

                ret=newInstance;
            }

            return (TDomainModel)ret;
        }

        /// <summary>Gets the <see cref="ICacheManager" /> associated to the
        /// <include file="../doc/Global.doc.xml" path="doc/template[@name='DataMapper']/*" />.</summary>
        protected ICacheManager CacheManager
        {
            get
            {
                return _CacheManager;
            }
        }



        #region IDataMapper
        /// <summary>Finds the <include file="../doc/Global.doc.xml" path="doc/template[@name='DomainModel']/*" /> associated to the specified key.</summary>
        IDomainModel IDataMapper.Find(IPrimaryKey key, Collections.DataMapperCollection dataMappers)
        {
            return Find((TPrimaryKey)key, dataMappers);
        }

        /// <summary>Refreshes the data of the specified <include file="../doc/Global.doc.xml" path="doc/template[@name='DomainModel']/*" /> instance.</summary>
        /// <param name="domainModel">The <include file="../doc/Global.doc.xml" path="doc/template[@name='DomainModel']/*" /> instance to be refreshed.</param>
        void IDataMapper.Refresh(IDomainModel domainModel)
        {
            Refresh((TDomainModel)domainModel);
        }

        /// <summary>Deletes the data of the specified <include file="../doc/Global.doc.xml" path="doc/template[@name='DomainModel']/*" /> instance.</summary>
        void IDataMapper.Delete(IDomainModel domainModel, IPersistenceContext context)
        {
            Delete((TDomainModel)domainModel, context);
        }

        /// <summary>Saves the data of the specified <include file="../doc/Global.doc.xml" path="doc/template[@name='DomainModel']/*" /> instance.</summary>
        void IDataMapper.Save(IDomainModel domainModel, IPersistenceContext context)
        {
            Save((TDomainModel)domainModel, context);
        }

        /// <summary>Gets the <include file="../doc/Global.doc.xml" path="doc/template[@name='DomainModel']/*" /> corresponding to the specified data.</summary>
        IDomainModel IDataMapper.GetDomainModel(IDomainModelDataTransferObject data, Collections.DataMapperCollection dataMappers)
        {
            return GetDomainModel(data, dataMappers);
        }

        /// <summary>Gets the data associated to the specified <include file="../doc/Global.doc.xml" path="doc/template[@name='DomainModel']/*" />.</summary>
        IDomainModelDataTransferObject IDataMapper.GetData(IDomainModel domainModel, bool deep)
        {
            return GetData((TDomainModel)domainModel, deep);
        }

        /// <summary>Assigns the specified data to the specified <include file="../doc/Global.doc.xml" path="doc/template[@name='DomainModel']/*" />.</summary>
        void IDataMapper.SetData(IDomainModel domainModel, IDomainModelDataTransferObject data)
        {
            SetData((TDomainModel)domainModel, data);
        }
        #endregion



        private DomainModelCreator<TDomainModel> _InstanceCreator;
        private ICacheManager _CacheManager;
    }

}
